#2477 - Fonts: Configurable font embedding policy (fsType / OnFontEmbedding - #2474
Open
swmal wants to merge 8 commits into
Open
#2477 - Fonts: Configurable font embedding policy (fsType / OnFontEmbedding#2474swmal wants to merge 8 commits into
swmal wants to merge 8 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fonts: Configurable font embedding policy (fsType / OnFontEmbedding)
The PDF exporter now respects the embedding restrictions a font declares through its OS/2 fsType field, instead of always subsetting unconditionally.
fsTypeis interpreted into aFontEmbeddingRestriction(None,NoSubsetting,NoEmbedding) viaOs2Table.GetEmbeddingRestriction(). The engine combines this with user configuration inOpenTypeFontEngine.ResolveEmbeddingDecision, applied by the document-wide subset builder (see below) before subsetting:Since fsType is a vendor declaration and not a technical lock, users who hold an embedding licence can override the default via
Workbook.ConfigureFonts(cfg => cfg.OnFontEmbedding(...)), returningSubset,EmbedWhole,Skip, orDefault. The decision is cached per font, so the callback fires once per font.New public types live in
EPPlus.Interfaces. Adds unit tests covering interpretation, default policy, and callback override. Wiki updated, see https://github.com/EPPlusSoftware/EPPlus/wiki/Configure-fonts#embedding-restrictionsFonts: Document-wide subsetting (DocumentFontSubsetBuilder)
Font subsetting has moved from a per-font model to a document-wide one. Previously each font subsetted independently, so when several fonts shared a fallback (an emoji font, or the Archivo Narrow last resort) each produced an incompatible glyph-id space — yielding oversized and, when fonts collided, corrupt PDFs where cells rendered each other's glyphs.
Subsetting is now owned by a single
DocumentFontSubsetBuilder(builder pattern): text is collected per requested font across the whole document, then oneBuild()runs all checks — embedding restrictions, fallback resolution, subset construction — producing one shared subset per distinct font.FontSubsetManageris removed;SingleFontSubsetterremains as the low-level single-font building block.Skipis now a real fallback path: a skipped font leaves the chain and its code points are redistributed, with the last-resort font guaranteeing non-empty output.On the PDF side,
PdfDictionariesowns the builder and a singleBuildSubsetsreplaces the old per-font loop. The font resource dictionary is populated only from fonts actually used after shaping, so skipped or unused fonts are no longer embedded — the root of the oversized-file bug.GetFontresolves a requested font to the one that actually renders it, so a font resolved via fallback still finds its resource. Text measurement shapes against whole fonts (advance widths are identical), avoiding redundant subsetting.Adds tests for the skip-as-fallback path, the shared-fallback collapse (the original corruption case), and all four export paths.
Merge note: rebased onto the per-worksheet printer-settings change. Multi-sheet layout currently replicates a single
PdfPageSettingsacross sheets (TODO(merge)inPdfCatalog.GetLayout); wiring per-sheet settings through is a coordinated follow-up. Font subsetting is document-global and unaffected.Known limitation (out of scope): uncommon subfamilies such as "Arial Black" resolve via the fallback chain rather than matching directly, since subfamily matching only recognises Regular/Bold/Italic/BoldItalic. Tracked separately.